
package com.ve.bluetooth;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.support.annotation.RequiresPermission;
import android.util.Log;

import java.io.IOException;
import java.util.UUID;


public class BluetoothService {
    private static final String TAG = BluetoothService.class.getSimpleName();
    public static String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
    public static final UUID SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
    public static final String[] NEEDED_PERMISSIONS = {
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.BLUETOOTH_PRIVILEGED,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION};

    protected static BluetoothService instance;
    protected Context context;
    protected BluetoothAdapter mAdapter;
    protected ConnectThread connectThread;
    protected BluetoothSocket bluetoothSocket;
    protected UUID uuid;
    protected BluetoothStatus mStatus;
    protected Handler handler;
    protected ConnectListener onEventCallback;
    protected ScanListener onScanCallback;
    protected boolean callListenersInMainThread;

    public BluetoothService(Context application) {
        context = application;
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mStatus = BluetoothStatus.NONE;
        callListenersInMainThread = true;
        uuid = SERIAL_UUID;
        handler = new Handler();
    }


    public void setOnEventCallback(ConnectListener onEventCallback) {
        this.onEventCallback = onEventCallback;
    }

    public void setOnScanCallback(ScanListener onScanCallback) {
        this.onScanCallback = onScanCallback;
    }

    protected synchronized void updateState(final BluetoothStatus status) {
        Log.v(TAG, "updateStatus() " + mStatus + " -> " + status);
        mStatus = status;

        if (onEventCallback != null)
            runOnMainThread(() -> onEventCallback.onStatusChange(status));
    }


    protected void runOnMainThread(Runnable runnable) {
        if (callListenersInMainThread) {
            handler.post(runnable);
        } else {
            runnable.run();
        }
    }


    public synchronized BluetoothStatus getStatus() {
        return mStatus;
    }


    @RequiresPermission(Manifest.permission.BLUETOOTH)
    public synchronized void connect(BluetoothDevice device) {
        disconnect();
        connectThread = new ConnectThread(device);
        connectThread.start();
        updateState(BluetoothStatus.CONNECTING);
    }

    public void disconnect() {
        if (connectThread != null) {
            connectThread.cancel();
            connectThread = null;
        }
    }


    @RequiresPermission(Manifest.permission.BLUETOOTH)
    private synchronized void connected(BluetoothSocket socket, final BluetoothDevice device) {
        this.bluetoothSocket = socket;
        if (onEventCallback != null) {
            runOnMainThread(() -> onEventCallback.onConnected(device));
        }
        updateState(BluetoothStatus.CONNECTED);
    }


    public synchronized void stopService() {
        disconnect();
        if (BluetoothService.instance == this) {
            BluetoothService.instance = null;
        }
    }


    private void connectionFailed() {
        updateState(BluetoothStatus.NONE);
        if (onEventCallback != null)
            runOnMainThread(() -> onEventCallback.onToast("连接失败"));
    }

    private void connectionLost() {
        updateState(BluetoothStatus.NONE);
        if (onEventCallback != null)
            runOnMainThread(() -> onEventCallback.onToast("连接断开"));
    }

    public BluetoothSocket getBluetoothSocket() {
        return bluetoothSocket;
    }


    private class ConnectThread extends Thread {
        private BluetoothSocket socket;
        private BluetoothDevice device;

        @RequiresPermission(Manifest.permission.BLUETOOTH)
        public ConnectThread(BluetoothDevice device) {
            this.device = device;


            try {
                socket = device.createRfcommSocketToServiceRecord(uuid);
                Log.d(TAG, "ConnectThread: uuid="+uuid);
                AudioManager mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
                mAudioManager.setMode(AudioManager.MODE_NORMAL);
                mAudioManager.setBluetoothScoOn(false);
                mAudioManager.setSpeakerphoneOn(true);
            } catch (Exception e) {
                socket=null;
                e.printStackTrace();
            }


        }

        @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN})
        public void run() {
            setName("ConnectThread");
            if (mAdapter.isDiscovering()) {
                mAdapter.cancelDiscovery();
            }
            try {
                socket.connect();
            } catch (Exception e) {
                e.printStackTrace();
                connectionFailed();

                try {
                    socket.close();
                } catch (Exception e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                return;
            }


            synchronized (BluetoothService.this) {
                connectThread = null;
            }

            connected(socket, device);
        }


        public void cancel() {
            try {

                socket.close();
                BluetoothAdapter.getDefaultAdapter().disable();
            } catch (Exception e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
            try {
                interrupt();
            } catch (Exception e) {
                Log.e(TAG, "interrupt() of Thread failed", e);
            }
        }
    }


    private final BroadcastReceiver mScanReceiver = new BroadcastReceiver() {
        @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH})
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothDevice.ACTION_FOUND.equals(action)) {

                final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                Log.d(TAG, "onReceive: "+device);
                final int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);

                if (onScanCallback != null)
                    runOnMainThread(() -> onScanCallback.onDeviceDiscovered(device, rssi));
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                stopScan();
            }
        }
    };

    @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH})
    public void startScan() {
        if (onScanCallback != null)
            runOnMainThread(() -> onScanCallback.onStartScan());

        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        context.registerReceiver(mScanReceiver, filter);

        if (mAdapter.isDiscovering()) {
            mAdapter.cancelDiscovery();
        }

        mAdapter.startDiscovery();
    }

    @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH})
    public void stopScan() {
        try {
            context.unregisterReceiver(mScanReceiver);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }

        if (mAdapter.isDiscovering()) {
            mAdapter.cancelDiscovery();
        }

        if (onScanCallback != null)
            runOnMainThread(() -> onScanCallback.onStopScan());
    }


    public static void init(Context application) {
        if (instance != null) {
            instance.stopService();
            instance = null;
        }
        instance = new BluetoothService(application);
    }


    public synchronized static BluetoothService getDefaultInstance() {
        if (instance == null) {
            throw new IllegalStateException("BluetoothService is not initialized. Call BluetoothService.init(config).");
        }
        return instance;
    }


    public static BroadcastReceiver createAutopairReceiver(final String pass) {
        return new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (ACTION_PAIRING_REQUEST.equals(action)) {
                    final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    try {
                        abortBroadcast();
                        ClsUtils.setPin(device.getClass(), device, pass);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        };
    }


}
